最初的模型:
浏览器下载 html
-
开始解析 html
遇见外链资源, 保存起来, 并且继续解析
html 解析结束
-
开始下载外链
-
下载结束
-
开始处理
css 处理
-
js 处理
处理完毕, 开始渲染
用户看到界面
-
-
这个模型的基础是: 浏览器是单线程的.
但是实际上: 浏览器不是单线程, 是多个线程.
浏览器有如下几个线程:
1 javascript引擎线程
2 界面渲染线程
3 浏览器事件触发线程
4 http请求线程
也就是说: 下载和解析是可以同步的, 遇见外链就开始下载.
更改之后的模型
浏览器下载 html
-
开始解析 html
遇见外链资源, 开始下载, 并且继续解析
html 解析结束
-
下载结束
-
开始处理
css 处理
-
js 处理
处理完毕, 开始渲染
用户看到界面
-
这个模型的基础是:
资源下载和 html 解析是同步的, 所有的资源下载结束, 才开始进行下一步:渲染.
实际情景是:
资源大致可以分成
css
js
imgs
others
imgs以及 others 这种, 如果一个资源过大, 比如说一个媒体文件100M, 非要等到用户下载结束
才开始下一步, 这显然是不合理的.
而 css 和 js 是可以对页面产生修改和效果的, 所以必须要等待它们的参与才能进行下一步操作,
比如说 css,js 都没有下载解析执行结束, 就开始下一步渲染, 最终渲染的结果是一个没有样式的
页面.
看浏览器是不是这么想的?
实验:
在demo 中加一个 p 标签, 在底部加一个 css 外链
如果 css 都没有加载完毕, p 标签就显示出来了, 说明浏览器就没有等 css 文件
也就是说: html 解析结束之后, 什么都不管, 就开始下一步渲染了.
demo:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<p>hello world</p>
<link rel="stylesheet" href="https://www.google.com.hk/test.css">
</body>
</html>
结果:
css 没有加载之前, 页面空白, 说明 html 解析结束之后, 会等到css加载出来再开始渲染.
更一步的实验:
资源变成 js, 页面先渲染出来, js 还在加载.
资源变成 img, 页面会先渲染出来, img 还在加载.
结论:
**html 解析结束之后, 会先等到 css 下载和解析结束之后(通过 link 标签知道是 css 文件)
再开始下一步, 并不会等所有的非 js 资源.**
所以模型变成:
浏览器下载 html
-
开始解析 html
遇见外链资源, 开始下载, 并且继续解析
html 解析结束
-
等到css下载结束
-
开始处理 css
处理完毕, 开始渲染
用户看到界面
-
现在考虑一种情况:
js 是有能力去改变 DOM, 那么如果都渲染结束了, js 这个时候开始执行了,然后把页面重新干掉了.
这个时候怎么办?
只能将修改应用到已经渲染好的页面上.
考虑一种极端情况, 页面里面有成千上万的节点, 比如说1万个节点, css 文件都 一两百k
辛辛苦苦浏览器把页面渲染出来了, 然后这个时候, js 下载结束开始执行, 啪的一下把页面
document.write('中奖啦');
这种情况肯定不能允许发生.
为了避免这种情况, 可以这样
先全局检测下是不是有 script 标签, 如果有的话, html 就等着 script 加载执行之后,
再开始渲染.
这种方式有一个不好: 也就是说, 即使我们现在有了 html 和 css, 其实都可以把页面渲染出来了
但是还是要等 script 下载执行之后才敢进行渲染, 有点投鼠忌器的感觉.
有时候等半天, 可能 script 返回的就一句话:
console.log('逗你玩');
而且全局检测 script 标签, 这句话说的简单, 实际上是要建立在你已经把 html 解析结束了之后才知道
到底有没有 script 标签.
所以实际的情况是:
浏览器不知道页面里面有没有 script 标签
不知道script 里面会不会有 DOM 操作, 是 '中奖啦' 还是 '逗你玩'
面对这种情况, 实际上只能赌, 或者说博弈.
浏览器拿到 html 和 css 之后依旧开始解析渲染
为了减小万一中奖之后全盘都输的情况, 当遇见 script 之后
停止解析, 专心下载 js 文件
这样即使中奖, 我也就渲染了前面了一点内容, 后面的还没有渲染, 输少点
但是这样后面如果还有资源要加载。。。
所以更新策略:
浏览器遇见 script, 开始下载, 把后面的html 解析掉, 所有的资源都开始下载
然后回头安心等这个 script, 看看到底中奖不中奖.
所以模型变成:
浏览器下载 html
-
开始解析 html
-
遇见外链, 开始下载
-
发现 script 外链, 继续html解析
-
将页面分成两部分, script 标签之前, 之后
-
处于 script 之前的页面
-
css 下载结束
渲染
-
处于 script 之后的页面
-
script 下载完毕, 执行
-
css 下载完毕
渲染
-
-
-
-
-
-
-
浏览器不是等所有的资源都下载结束才开始渲染
浏览器也不是等到所有的 js 都执行结束之后才会渲染
具体的渲染过程
当 html 解析成 DOM tree, css 解析成 CSSOM, 二者合并成
Render Tree, 就可以开始渲染了.
首先要先计算这棵树上面的所有的节点的位置, 这一步叫做 layout
然后要给每个节点上色, 这一步叫做 paint
layout 和 paint 统称为 render.
当页面的元素的位置修改之后, 就会出现 relayout (重绘)
relayout 必定会造成 repaint.
附录: defer, async 之间的区别.
defer
之前说了, 浏览器遇见 js 之后会始终等着它执行, 后面的内容都不渲染了
然后经常就报错了, 我草获取某个节点怎么没有, 想要获取后面的节点但是节点还没有
渲染出来, 所以呢, 所以就有了 defer
相当于说, 我等你全部渲染之后再执行吧, 要不然我老是出错, 烦人.
执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。
async
还有一种情况, 就是说, 浏览器辛辛苦苦等到 js 下载执行结束发现哎吆我草这个玩意
对dom屁改动都没有, 我等她干嘛啊, 我草草, 那就出来 async
这个东西就是说, 不需要等我, 也不需要关心我的执行, 我不会干扰你的, 你做自己的事情就好.
同时也解决了多个js之间的依赖, 加上这个就表示, 我是孤立的, 我不依赖任何其他js也不给任何其他
js 依赖.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。